/* 
 * File:   Sesion.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 18, 2012, 7:00 PM
 */

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "server_Sesion.h"
#include "common_Thread.h"
#include "common_ProtocoloConstantes.h"

using namespace TP::Threading;

using namespace std;
using namespace TP::Comunicacion;

#define CONEXION_FINALIZADA 0
#define CONEXION_ERROR -1
#define BUFFER_SIZE 256

Sesion::Sesion(int pFileDescriptor, struct sockaddr_in* pClientAddress){
    
    this->fileDescriptor = pFileDescriptor;
    this->clientAddress = pClientAddress;
    this->timeout = 0;
}

Sesion::Sesion(int pFileDescriptor, struct sockaddr_in* pClientAddress, const int &pTimeout){

    this->fileDescriptor = pFileDescriptor;
    this->clientAddress = pClientAddress;
    
    if (pTimeout > 0)
        this->timeout = pTimeout;
    else
        this->timeout = 0;
}

Sesion::~Sesion(){
    if (this->clientAddress)
        delete this->clientAddress;
}

void Sesion::agregarMensajeEntranteManejador(IMensajeEntranteManejador* pManejador){
    if (pManejador)
        this->mensajeSesionManejadores.push_back(pManejador);
}

void Sesion::agregarManejadorSesionEvento (IManejadorSesionEvento* pManejador){
    if (pManejador)
        this->manejadorSesionEvento = pManejador;
}

void Sesion::Iniciar() throw (Excepcion&){
    
    try
    {
        this->conexionFinalizada = false;
        
        // Inicio la tarea de la sesion.
        Thread::Iniciar();
    }
    catch (Excepcion& pEx)
    {
        throw;
    }

}

void Sesion::Finalizar() throw (Excepcion&){
    try
    {
        // Cierro la conexion en caso de que se encuentre activa.        
        if (!this->conexionFinalizada)
        {
            this->conexionFinalizada = true;
            shutdown(this->fileDescriptor, SHUT_RDWR );
        }
        
        // Finalizo las operaciones.
        Thread::Finalizar();
    }
    catch (Excepcion& pEx)
    {
        throw;
    }
}

void Sesion::Responder (const string& pMensaje) throw (Excepcion&) {
    
    try
    {
        /* Toma el mensaje, y lo escribe a traves de la conexion.
         */
        string msg_env = pMensaje + MARCA_FIN_MENSAJE;

        // Envio el mensaje.
        
        int intentos = this->timeout;
        uint32_t cntBytes = 0;
        uint32_t byteIndex = 0;
        
        while (true)
        {
            cntBytes = send (this->fileDescriptor, &(msg_env.c_str()[byteIndex]), msg_env.size()- byteIndex, 0);
            int errnov = errno;
            
            if (cntBytes > 0)
            {
                byteIndex += cntBytes;
                
                if (msg_env.length() == byteIndex)
                    break;
            }
            else
            {
                intentos--;
                if (intentos < 0) throw errnov;
            }
        }
    }
    catch (int &ex)
    {
        string msg = "Error de comunicacion con el cliente.  ";
        decodificarError (msg, ex);
        
        throw Excepcion (msg);
    }    
}

void Sesion::tarea(){
    
    if (this->conexionFinalizada) return;
    
    /* La sesion tiene como tarea recibir los mensajes entrantes, desencodearlos
     * y notificar el mensaje a sus suscriptores.
     * 
     * La tarea es valida siempre y cuando la conexion no haya sido finaliada
     * por el host.
     */
    char* buffer = NULL;
    
    try
    {
        uint32_t longitudMarca = strlen(MARCA_FIN_MENSAJE);
        buffer = new char [longitudMarca + 1];
        string mensaje = "";
        int intentos = this->timeout;
        
        // Se llena el buffer por primera vez.

        // Inicializo el buffer para lectura.

        intentos = this->timeout;
        
        bzero(buffer,longitudMarca + 1);
        
        uint32_t indiceByte = 0;

        while (indiceByte < longitudMarca)
        {
            int bytes_leidos = recv (this->fileDescriptor, &(buffer[indiceByte]), sizeof (char), 0);
            int errnov = errno;

            if (bytes_leidos <= 0) 
            {
                intentos--;
                if (intentos < 0) // Timeout.
                    throw errnov;
            }
            else
            {
                indiceByte++;
            }
        }

        /* Reiniciar el mensaje.
         * 
         */
        mensaje = "";
        intentos = this->timeout;

        while (! this->conexionFinalizada)
        {
            if (0 == strcmp (buffer, MARCA_FIN_MENSAJE))
            {
                if (mensaje.length() > 0)
                {
                    this->notificarMensaje(mensaje);
                }
                
                // Inicializo el buffer para lectura.
                
                intentos = this->timeout;
                bzero(buffer,longitudMarca + 1);
                uint32_t indiceByte = 0;

                while (indiceByte < longitudMarca)
                {
                    int bytes_leidos = recv (this->fileDescriptor, &(buffer[indiceByte]), sizeof (char), 0);
                    int errnov = errno;

                    if (bytes_leidos <= 0) 
                    {
                        intentos--;
                        if (intentos < 0) // Timeout.
                            throw errnov;
                    }
                    else
                    {
                        indiceByte++;
                    }
                }
                
                /* Reiniciar el mensaje.
                 * 
                 */
                mensaje = "";                                
                intentos = this->timeout;
            }
            else
            {
                // Se lee del buffer el proximo byte.
                
                mensaje += (char) buffer[0];
                
                
                // Se actualiza el buffer.
                uint32_t i = 0;
                while (i < longitudMarca)
                {
                    buffer[i] = buffer[i+1];
                    i++;
                }

                // Se lee el proximo byte desde el socket.
                
                int bytes_leidos = recv (this->fileDescriptor, &(buffer[longitudMarca - 1]), sizeof (char), 0);
                int errnov = errno;

                if (bytes_leidos <= 0) 
                {
                    intentos--;
                    
                    if (intentos < 0 || bytes_leidos == CONEXION_ERROR) // Timeout.
                        throw errnov;
                    
                    else if (bytes_leidos == 0)
                        throw CONEXION_FINALIZADA;
                }
            }
        }    
        
        delete [] buffer;
    }
    catch (int ex)
    {
        if (NULL != buffer)
        {
            delete [] buffer;
        }
        
        this->manejarErrorComunicacion(ex); 
    }
}

const string& Sesion::notificarMensaje (const string& pMensaje){

    // Notifico el mensaje a los servicios.
    for (vector<IMensajeEntranteManejador*>::iterator it = this->mensajeSesionManejadores.begin(); it != this->mensajeSesionManejadores.end(); ++it)
    {
        (*it)->mensajeEntranteManejador((IMensajero*) this, pMensaje);                
    }
    
    return pMensaje;
}

void Sesion::manejarErrorComunicacion (int pEstadoComunicacion){
    
    if (!this->conexionFinalizada) /*Finalizada por el servidor.*/
    {
        if (CONEXION_FINALIZADA == pEstadoComunicacion)
        {
            string msg = "CONEXION FINALIZADA.";
            decodificarError(msg, pEstadoComunicacion);
            this->manejadorSesionEvento->eventoErrorComunicacion(reinterpret_cast<IEnviante*> (this), msg);
            this->conexionFinalizada = true;

            // Cierro el descriptor.
            close (this->fileDescriptor);
        }
        else if (CONEXION_ERROR == pEstadoComunicacion)
        {
            string msg = "ERROR DE CONEXION.";
            decodificarError(msg, pEstadoComunicacion);
            this->manejadorSesionEvento->eventoErrorComunicacion((IEnviante*)this, msg);
        }

    }
}

void Sesion::decodificarError (string &pMensaje, int& pCodigoError){
    
    switch (pCodigoError)
    {
        case EACCES:
        pMensaje += "(For UNIX domain sockets, which are identified by pathname) Write permission is denied on the destination socket file, or search permission is denied for one of the directories the path prefix. (See path_resolution(7).).  (For UDP sockets) An attempt was made to send to a network/broadcast address as though it was a unicast address.";
        break;

        case EAGAIN:
        pMensaje += "The socket is marked nonblocking and the requested operation would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities. ";
        break;

        case EBADF:
        pMensaje += "An invalid descriptor was specified.";
        break;

        case ECONNRESET:
        pMensaje += "Connection reset by peer.";
        break;

        case EDESTADDRREQ:
        pMensaje += "The socket is not connection-mode, and no peer address is set.";
        break;

        case EFAULT:
        pMensaje += "An invalid user space address was specified for an argument.";
        break;

        case EINTR:
        pMensaje += "A signal occurred before any data was transmitted; see signal(7).";
        break;

        case EINVAL:
        pMensaje += "Invalid argument passed.";
        break;

        case EISCONN:
        pMensaje += "The connection-mode socket was connected already but a recipient was specified. (Now either this error is returned, or the recipient specification is ignored.) ";
        break;

        case EMSGSIZE:
        pMensaje += "The socket type requires that message be sent atomically, and the size of the message to be sent made this impossible. ";
        break;

        case ENOBUFS:
        pMensaje += "The output queue for a network interface was full. This generally indicates that the interface has stopped sending, but may be caused by transient congestion. (Normally, this does not occur in Linux. Packets are just silently dropped when a device queue overflows.) ";
        break;

        case ENOMEM:
        pMensaje += "No memory available.";
        break;

        case ENOTCONN:
        pMensaje += "The socket is not connected, and no target has been given. ";
        break;

        case ENOTSOCK:
        pMensaje += "The argument sockfd is not a socket. ";
        break;

        case EOPNOTSUPP:
        pMensaje += "Some bit in the flags argument is inappropriate for the socket type. ";
        break;

        case EPIPE:
        pMensaje += "The local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set. ";
        break;
    }
}